package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.entity.vo.CategoryLevel2Vo;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 列表查询，以树形列表返回
     *
     * @author 胡菜鸡
     * @date 2021/12/14 16:18
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类（这里的baseMapper对象是父类ServiceImpl携带的）
        List<CategoryEntity> categories = baseMapper.selectList(null);
        //2.组装成父子的树形结构
        List<CategoryEntity> treeMenus = getTreeList(0L, categories);
        return treeMenus;
    }

    /**
     * 将List列表改造为树型列表
     *
     * @author 胡菜鸡
     * @date 2021/12/14 17:33
     */
    private List<CategoryEntity> getTreeList(Long parentId, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            //过滤集合找到父ID正确的内容（这里因为是Long型值所以不能用 == 比较）
            return parentId.equals(categoryEntity.getParentCid());
        }).map(categoryEntity -> {
            //获取子集合的子集
            categoryEntity.setChildren(getTreeList(categoryEntity.getCatId(), all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            //按照数据库标识的字段进行排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

    /**
     * 批量删除
     *
     * @author 胡菜鸡
     * @date 2021/12/15 10:08
     */
    @Override
    public int removeMenuByIds(List<Long> catIds) {
        //TODO 1、检查当前删除的菜单是否被别的地方引用

        //2、进行批量删除（逻辑删除）
        return baseMapper.deleteBatchIds(catIds);
    }

    /**
     * 通过分类id找到完整路径[父/子/孙]
     *
     * @author 胡菜鸡
     * @date 2021/12/20 14:26
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        paths = findParentPath(catelogId, paths);

        return paths.toArray(new Long[paths.size()]);
    }

    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
        paths.add(entity.getCatId());
        return paths;
    }

    /**
     * 级联修改
     *
     * @author 胡菜鸡
     * @date 2021/12/20 15:51
     */
    //批量删除（方法一：利用 @Caching 进行批量缓存操作）
//    @Caching(evict = {
//            //代表当前方法需要清除缓存
//            @CacheEvict(value = "category", key = "'level1Categorys'"),
//            @CacheEvict(value = "category", key = "'getCatalogJson'")
//    })
    //批量删除（方法二：利用 allEntries=true 实现删除value指定的所有分区数据）
    @CacheEvict(value = "category", allEntries = true)
//    @CachePut   //双写模式
    @Override
    @Transactional
    public boolean updateCascade(CategoryEntity category) {
        System.out.println("调用了方法：updateCascade...");
        this.updateById(category);

        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());

        return true;
    }

    /**
     * 查询商品一级分类
     * @author 胡菜鸡
     * @date 2022/1/7 17:21
     */
    //代表当前方法的结果需要缓存（缓存中有，则不调用方法；没有则调用方法并将结果放入缓存）
    @Cacheable(
            value = {"category"}, //缓存分区的名称，可以同时放入多个缓冲区（大括号包起来）
            key = "'level1Categorys'",   //缓冲文本键值，这里检测的是表达式（value::key为整体名称）
            sync = true         //缓存读写过程是否加本地锁（为true则解决缓存击穿问题）
    )
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("调用了方法getLevel1Categorys。。。");
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getParentCid, 0);
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(wrapper);

        return categoryEntities;
    }

    /**
     * 使用 springcache 缓存查询三级分类
     * @author 胡菜鸡
     * @date 2022/1/7 17:25
     */
    @Cacheable(value = "category", key = "#root.methodName")    //将 value::方法名 作为缓存名称
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatalogJson() {
        return getCatalogJsonFromDB();
    }

    /**
     * 使用redis查询三级分类
     *
     * @author 胡菜鸡
     * @date 2022/1/6 15:02
     */
    public Map<String, List<CategoryLevel2Vo>> getCatalogJson2() {
        /*
            序列化与反序列化： JSON <-> 数据类型
            redis查询需要解决的三个问题：
                缓存穿透：允许存null
                缓存雪崩：给过期值上再加个随机值
                缓存击穿：加锁
         */
        //看看redis中是否保存了数据
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            //缓存中有数据 -> 将缓存中的JSON转为需要的类型后返回
            return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {
            });
        }
        /*
        //加锁（本地锁）
        synchronized (this) {
            String catalogJsonAgain = redisTemplate.opsForValue().get("catalogJson");
            if (!StringUtils.isEmpty(catalogJsonAgain)) {
                //缓存中有数据 -> 将缓存中的JSON转为需要的类型后返回
                return JSON.parseObject(catalogJsonAgain, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {
                });
            }

            Map<String, List<CategoryLevel2Vo>> result = getCatalogJsonFromDB();
            //将对象转为JSON存入缓存（因为JSON可跨平台兼容）
            redisTemplate.opsForValue().set("catalogJson", JSON.toJSONString(result), 1, TimeUnit.MINUTES);
            return result;
        }
        */

        //加锁（分布式锁 -> 利用redisson完成）
        return getCatalogJsonWithRedissonLock();
    }

    /**
     * 分布式锁redis查询
     *
     * @author 胡菜鸡
     * @date 2022/1/6 17:16
     */
    public Map<String, List<CategoryLevel2Vo>> getCatalogJsonWithRedissonLock() {
        RLock lock = redissonClient.getLock("catalogJson-lock");
        lock.lock();        //上锁

        System.out.println("获取分布式锁成功");

        Map<String, List<CategoryLevel2Vo>> result = null;
        try {
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if (!StringUtils.isEmpty(catalogJson)) {
                //缓存中有数据 -> 将缓存中的JSON转为需要的类型后返回
                return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {
                });
            }

            result = getCatalogJsonFromDB();
            //将对象转为JSON存入缓存（因为JSON可跨平台兼容）
            redisTemplate.opsForValue().set("catalogJson", JSON.toJSONString(result), 5, TimeUnit.MINUTES);
        } finally {
            lock.unlock();  //开锁
        }

        return result;

    }

    /**
     * 从数据库查询三级分类
     *
     * @author 胡菜鸡
     * @date 2022/1/6 15:02
     */
    public Map<String, List<CategoryLevel2Vo>> getCatalogJsonFromDB() {
        System.out.println("查询了数据库");

        /* 查出所有分类（用于后面过滤，目的在于将数据库的多次查询变为一次） */
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(null);

        //查出所有1级分类
        List<CategoryEntity> level1Categorys = getChildren(categoryEntities, 0L);
        //封装数据
        Map<String, List<CategoryLevel2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(
                level1Category -> level1Category.getCatId().toString(),       //将一级分类的id作为key值
                level1Category -> {                              //将一级分类映射出的二级分类对象作为value
                    //查出一级分类对应的二级分类
                    List<CategoryEntity> level2Categorys = getChildren(categoryEntities, level1Category.getCatId());

                    //将二级分类封装成为 Vo 对象（用于controller层返回）
                    List<CategoryLevel2Vo> categoryLevel2Vos = null;
                    if (level2Categorys != null && level2Categorys.size() > 0) {
                        categoryLevel2Vos = level2Categorys.stream().map(level2Category -> {
                            //找寻二级分类的三级分类
                            List<CategoryEntity> level3Categorys = getChildren(categoryEntities, level2Category.getCatId());
                            //封装成Vo
                            List<CategoryLevel2Vo.CategoryLevel3Vo> level3Vos = null;
                            if (level3Categorys != null && level3Categorys.size() > 0) {
                                level3Vos = level3Categorys.stream().map(level3Category -> {
                                    return new CategoryLevel2Vo.CategoryLevel3Vo(level2Category.getCatId(), level3Category.getCatId(), level3Category.getName());
                                }).collect(Collectors.toList());
                            }

                            //用三级分类的vo对象创建二级分类的vo对象
                            return new CategoryLevel2Vo(level1Category.getCatId(), level3Vos, level2Category.getCatId(), level2Category.getName());
                        }).collect(Collectors.toList());
                    }
                    return categoryLevel2Vos;
                })
        );
        return collect;
    }

    /**
     * 从所有分类中查找子分类
     *
     * @param categoryEntities 从哪个集合中查找
     * @param parentId         需要查找哪个ID的子分类
     * @return 子分类集合
     * @author 胡菜鸡
     * @date 2022/1/6 11:37
     */
    private List<CategoryEntity> getChildren(List<CategoryEntity> categoryEntities, Long parentId) {
        return categoryEntities.stream().filter(categoryEntity ->
                parentId.equals(categoryEntity.getParentCid())
        ).collect(Collectors.toList());
    }

}